
// ===============================================================================================================
// -*- C++ -*-
//
// ShaderProgram.cpp - GPU shader program interface.
//
// Copyright (c) 2011 Guilherme R. Lampert
// guilherme.ronaldo.lampert@gmail.com
//
// This code is licenced under the MIT license.
//
// This software is provided "as is" without express or implied
// warranties. You may freely copy and compile this source into
// applications you distribute provided that the copyright text
// above is included in the resulting source code.
//
// ===============================================================================================================

#include <GfxLib.hpp>

namespace GfxLib {

ShaderProgram::ShaderProgram(const char ** srcFiles, size_t numFiles)
{
	assert(srcFiles != 0 && numFiles > 0);

	programObj = glCreateProgram();

	for (size_t i = 0; i < numFiles; ++i)
	{
		GLenum shaderType;
		GLuint shader;
		char * buffer;

		const char * ext = strrchr(srcFiles[i], '.');

		if ((ext != 0) && (ext[1] != 0))
		{
			// Case-insensitive compare:
			if (stricmp(ext, ".vert") == 0)
			{
				shaderType = GL_VERTEX_SHADER;
			}
			else if (stricmp(ext, ".frag") == 0)
			{
				shaderType = GL_FRAGMENT_SHADER;
			}
			else if (stricmp(ext, ".geom") == 0)
			{
				shaderType = GL_GEOMETRY_SHADER_EXT;
			}
			else
			{
				CommLib::DbgPrintf(PRINT_ERROR, "Invalid shader file extension! Must be either .vert or .frag or .geom");
				continue;
			}
		}
		else
		{
			CommLib::DbgPrintf(PRINT_ERROR, "File without extension -- (Unknown type) -- File Name: %s", srcFiles[i]);
			continue;
		}

		FILE * file = fopen(srcFiles[i], "rb");

		if (file != 0)
		{
			fseek(file, 0, SEEK_END);
			long fileSize = ftell(file);
			fseek(file, 0, SEEK_SET);

			buffer = new char [fileSize + 1];

			fread(buffer, sizeof(char), fileSize, file);
			fclose(file);

			buffer[fileSize] = 0;

			shader = glCreateShader(shaderType);
			glShaderSource(shader, 1, const_cast<const char **>(&buffer), 0);

			delete[] buffer;

			glCompileShader(shader);
			glAttachShader(programObj, shader);

			shaders.push_back(shader);

			if (shaderType == GL_GEOMETRY_SHADER_EXT)
			{
				// Set Geometry Shader special parameters:
				glProgramParameteriEXT(programObj, GL_GEOMETRY_INPUT_TYPE_EXT, GL_TRIANGLES);
				glProgramParameteriEXT(programObj, GL_GEOMETRY_OUTPUT_TYPE_EXT, GL_TRIANGLE_STRIP);

				GLint temp;
				glGetIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT, &temp);
				glProgramParameteriEXT(programObj, GL_GEOMETRY_VERTICES_OUT_EXT, temp);
			}
		}
		else
		{
			CommLib::DbgPrintf(PRINT_ERROR, "Failed to open shader source file: \"%s\"", srcFiles[i]);
		}
	}

	glLinkProgram(programObj);

	WriteInfoLog();
}

ShaderProgram::ShaderProgram(const char ** shaderCodes, size_t count, const int * shaderTypes)
{
	assert(shaderCodes != 0 && count > 0 && shaderTypes != 0);

	programObj = glCreateProgram();

	for (size_t i = 0; i < count; ++i)
	{
		assert(shaderTypes[i] == GL_VERTEX_SHADER || shaderTypes[i] == GL_FRAGMENT_SHADER || shaderTypes[i] == GL_GEOMETRY_SHADER_EXT);

		GLuint shader = glCreateShader(shaderTypes[i]);
		glShaderSource(shader, 1, const_cast<const char **>(&shaderCodes[i]), 0);

		glCompileShader(shader);
		glAttachShader(programObj, shader);

		shaders.push_back(shader);

		if (shaderTypes[i] == GL_GEOMETRY_SHADER_EXT)
		{
			// Set Geometry Shader special parameters:
			glProgramParameteriEXT(programObj, GL_GEOMETRY_INPUT_TYPE_EXT, GL_TRIANGLES);
			glProgramParameteriEXT(programObj, GL_GEOMETRY_OUTPUT_TYPE_EXT, GL_TRIANGLE_STRIP);

			GLint temp;
			glGetIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT, &temp);
			glProgramParameteriEXT(programObj, GL_GEOMETRY_VERTICES_OUT_EXT, temp);
		}
	}

	glLinkProgram(programObj);

	WriteInfoLog();
}

void ShaderProgram::Enable(void) const
{
	glUseProgram(programObj);
}

void ShaderProgram::Disable(void) const
{
	glUseProgram(0); // Switch back to fixed functionality
}

void ShaderProgram::SetUniform1i(const char * name, int i)
{
	GLint uniLoc = glGetUniformLocation(programObj, name);
	glUniform1i(uniLoc, i);
}

void ShaderProgram::SetUniform1f(const char * name, float f)
{
	GLint uniLoc = glGetUniformLocation(programObj, name);
	glUniform1f(uniLoc, f);
}

ShaderProgram::~ShaderProgram(void)
{
	if (!shaders.empty())
	{
		std::vector<GLuint>::const_iterator it = shaders.begin();
		std::vector<GLuint>::const_iterator end = shaders.end();

		while (it != end)
		{
			glDetachShader(programObj, *it);
			glDeleteShader(*it);
			++it;
		}

		shaders.clear();
	}

	glDeleteProgram(programObj);
}

void ShaderProgram::WriteInfoLog(void) const
{
	try {

		GLsizei infoLogLen;
		GLsizei charsWritten;
		std::string infoLogStr;

		std::vector<GLuint>::const_iterator it = shaders.begin();
		std::vector<GLuint>::const_iterator end = shaders.end();

		while (it != end)
		{
			infoLogLen = 0;
			charsWritten = 0;
			char * shaderLogPtr;

			glGetShaderiv(*it, GL_INFO_LOG_LENGTH, &infoLogLen);

			if (infoLogLen > 1)
			{
				shaderLogPtr = new char [infoLogLen + 1];
				glGetShaderInfoLog(*it, infoLogLen, &charsWritten, shaderLogPtr);
				shaderLogPtr[infoLogLen] = 0;
				infoLogStr.append(shaderLogPtr);
				delete[] shaderLogPtr;
			}

			++it;
		}

		infoLogLen = 0;
		charsWritten = 0;
		char * programLogPtr;

		glGetProgramiv(programObj, GL_INFO_LOG_LENGTH, &infoLogLen);

		if (infoLogLen > 1)
		{
			programLogPtr = new char [infoLogLen + 1];
			glGetProgramInfoLog(programObj, infoLogLen, &charsWritten, programLogPtr);
			programLogPtr[infoLogLen] = 0;
			infoLogStr.append(programLogPtr);
			delete[] programLogPtr;
		}

		if (!infoLogStr.empty())
		{
			// Send to the defaul log:
			CommLib::DbgPrintf(PRINT_MSG, "SHADER PROGRAM INFO LOG:");
			CommLib::DbgPrintf(PRINT_MSG, infoLogStr.c_str());
		}
	}
	catch (...) {

		CommLib::DbgPrintf(PRINT_ERROR, "Unable to generate the shader program info log! Unknown error...");
	}
}

}; // namespace GfxLib {}